吵翻了!到底该选 Rust 还是 Go,成2023年最大技术分歧
2023 年,我们有一千个学习 Rust 的理由。
8 月 7 日,Rust 基金会发布了 2022 年度 Rust 调查报告结果,报告显示 Rust 采用率不断提高,超过 90% 的调查受访者表示自己是 Rust 用户;29.7% 的受访者表示,他们在工作中的大部分编码工作都使用 Rust,比上一年显着增加了 51.8%。
毋庸置疑,Rust 以其卓越的内存安全性和并发性能正日益成为开发者关注的焦点。然而,同样令人难以忽视的是 Go,这门曾被评选为年度编程语言的相对比较“老牌”的选手。
Go 语言诞生于 2009 年,一开始就因其独特的并发模型和强大的性能优势而受到了极大关注。值得注意的是,跟 Rust 语言一样,Go 语言的创建者也同样“讨厌”C++,并且 Go 同样也都是云原生的主导语言。
而在 Stack Overflow 2022 开发者调查中,对于“让人爱恨交织的编程语言”这个问题,在 7 万份回复中,程序员们明显也更为偏爱 Rust,86% 的人表示喜欢 Rust,而 64% 的人表示喜欢 Go。面对 Rust 的火爆现状,一些开发者发出了灵魂提问:2023 年,Go 还值得学习吗?
另外,这两天,到底是该选 Rust 还是选 Go,也成为了 Hacker News 上的一个热门话题:
一位挺 Rust 的网友说道:“我也为这个选择烦恼了很久。最终 Rust 胜出了。首先,我感觉 Rust 更接近于以前 Pascal 时代的东西,你可以控制一切;其次,如果 wasm 和相关技术大爆发,Rust 将是一个更安全的选择;然后,我们已经有了 Python 用于快速开发,因此选择一些更极端的东西是有道理的,Go 在某种程度上处于中间地带。最后,Rust 应用于内核且备受关注,所以不太可能会被淘汰。”
另一位持反对意见的开发者则表示,“我从事 Go 开发已经快十年了,但最近我也尝试了下 Rust。我认为目前有一些对 Rust 的强制性和误导性偏好,从我在各种初创公司的经验,包括我目前所在的公司来看,对于后端开发来说,Go 是迄今为止最佳选择!注意,在性能、功能或其他方面……这两种语言非常非常相似!”
不得不说的是,Go 和 Rust 绝对都是优秀的编程语言。它们现代、强大、应用广泛,而且有着卓越的性能表现。但如果直接对比 Go 和 Rust 谁更好之类的,真的没啥意义,因为每种编程语言都代表着背后一系列深层次的权衡。不同的语言会针对不同的需求进行优化,因此我们在选择语言时,也应该考虑自己想要用它解决什么样的问题。所以我们将从 Go 和 Rust 语言的适用场景出发,探讨下 Go 与 Rust 的设计之“道”。
虽然 Rust 和 Go 在语法和风格上差别很大,但它们都是构建软件的一流工具。下面咱们开始具体分析。
Rust 和 Go 有很多共同点,所以人们才经常把二者拿来相提并论。那它们有哪些共同目标?
Rust 是一种低级静态类型的多范式编程语言,更多关注安全性和性能。
—Gints Dreimanis
而:
Go 是一种开源编程语言,能够轻松构建起简单、可靠且高效的软件。
—golang.org
Go 和 Rust 都属于重视内存安全的现代编程语言。在 C 和 C++ 等旧语言发展的这几十年间,我们已经清楚地意识到,引发错误和 bug 的核心原因之一,就是对内存的不安全 / 不正确访问。
于是 Rust 和 Go 各自给出了不同的解决思路,但二者的目标都是在内存管理方面更智能、更安全,帮助开发者编写出正确且性能极佳的程序。
二者都属于编译语言,也就是说可以将程序直接翻译成可执行的机器代码,这样就能把程序部署成单一二进制文件。跟 Python 和 Ruby 等解释性语言不同,我们不需要随程序一同发布解释器和大量的库 / 依赖项。作为这个核心优势的直接体现,Rust 和 Go 程序的运行速度往往比解释性语言更快。
Rust 和 Go 都属于功能强大且可扩展的通用编程语言,大家可以用它们开发出各种现代软件——从 Web 应用程序到分布式微服务,还包括嵌入式微控制器和移动应用等等。
两者都拥有优秀的标准库和蓬勃发展的第三方生态系统,外加强大的商业支持与庞大的用户基础。二者已经存在多年,并将在未来几年继续保持旺盛的发展势头。如今,学习 Go 或者 Rust 将是非常合理的时间和精力投入方向。
两者既不过多偏向函数式语言(例如 Scala 或 Elixir),也不完全面向对象(例如 Java 和 C#)。相反,虽然 Go 和 Rust 都具备函数式及面向对象编程的功能,但却始终强调务实取向——即以最合适的方式解决问题,而不是通过“意识形态”强迫大家用特定的方法做事。
但如果您确实喜欢函数式编程风格,那 Rust 这边的相关工具选项更多,这也是 Rust 优于 Go 的一点。
我们当然可以争论什么才是真正“面向对象”的语言。但公平地讲,C++、Java 或者 C# 用户所期望的那种面向对象编程风格,在 Go 或者 Rust 中确实不存在。 —Jack Mott
Rust 和 Go 都为大规模编程提供不少有用功能,所以它们都能适应大开发团队作战和大体量代码库的现实需求。
例如,C 程序员多年来一直在争论应该把括号放在哪里,还有代码要不要用制表符或空格进行缩进;但 Rust 和 Go 早已使用标准格式化工具(Go 有 gofmt,Rust 则是 rustfmt)彻底解决了这些问题。它们会使用符合规范的风格自动重写你的代码。
并不是说这种特定的格式有多精妙,而是 Rust 和 Go 程序员更加务实、宁愿选择统一的执行标准。
gofmt 的风格也许不是每个人的最爱,但 gofmt 却能帮到每一个人。 —Rob Pike
这两种语言的另一大优势,体现在构建管线上。二者都有优秀、内置且性能出色的标准构建与依赖项管理工具。就是说程序员不必跟复杂的第三方构建系统对抗,也用不着每隔几年就学习一种新系统。
我在职业生涯早期用的是 Java 和 Ruby,所以编写 Go 和 Rust 代码一直让我有点畏惧、觉得自己掌握不了。但等到进入谷歌并看到用 Go 编写的服务时,我才真正松了口气,因为我发现它很容易构建和运行。 Rust 也是如此。虽然我只在小规模项目上进行过研究,但也看得出它的易用性。我希望那些能够无限配置的构建系统早点成为历史,现在的新语言都附带自己的专用构建工具而且能够开箱即用,这样不好吗? —Sam Rose
聊了这么多问题,再加上两种语言都设计得如此精良、功能如此强大,那这场比拼到底有没有结果?或者说,既然二者都是非常出色的选项,那为什么人们还会在社交媒体上出离愤怒,撰写长篇累牍的评论博文放出“白痴才用 Rust”或者“Go 根本不能算编程语言”之类的狠话?
有些人当然只是为了宣泄情绪,但这显然无助于解决实际问题。至少在项目中该用哪种语言、或者该靠哪种语言闯荡编程世界这种事上,嗓门大显然无助于做出正确选择。
下面咱们回到成年人的讨论,看看理性分析之下 Rust 和 Go 之间如何互有长短。
之前已经提到,Go 和 Rust 生成的程序运行速度都很快,因为它们会被编译成本机机器码,无需通过解释器或虚拟机这个步骤。
但 Rust 的性能还是要更胜一筹,甚至能够与被称为业界性能标杆的 C 和 C++ 相媲美。而且跟这些老牌语言不同的是,Rust 还提供内存安全与并发安全机制,同时几乎不影响执行速度。Rust 还允许开发者构建复杂抽象,又无需在运行时承受性能损失。
相比之下,虽然 Go 程序的性能也不错,但其设计重心主要在于开发速度(包括编译)、而非执行程度。Go 程序员更倾向于代码的清晰可读,所以运行速度要稍逊几分。
Go 编译器也不会花费太多时间来生成最高效的机器码,它更关心如何快速编译大量代码。所以在运行时基准测试中,往往是 Rust 程序要压 Go 程序一头。
Rust 的运行时性能还具有良好的一致性和可预测性,因为它没有使用垃圾收集。Go 的垃圾收集器非常高效,而且做了优化以尽可能缩短暂停时长(随着 Go 新版本的发布,暂停时长也是越来越短)。但无论如何,垃圾收集总会给程序的行为方式带来一些不可预测性,而这对某些特定应用(比如嵌入式系统)而言可能很严重、甚至完全不可接受。
因为 Rust 的目标是让程序员完全控制底层硬件,所以 Rust 程序都能深度优化以接近机器的最大理论性能。如此一来,Rust 就在执行速度胜过其他一切的特定应用场景下成为最佳选项,此类用例包括游戏编程、操作系统内核、网络浏览器组件和实时控制系统等。
如果一种编程语言过于难学、把大多数人都挡在了门外,那它的性能再强也没有意义。Go 在设计上似乎就是刻意要跟 C++ 等复杂度不断提升的语言区分开来:它语法极少,关键字也极少,就连功能都不多。
这意味着 Go 语言很容易上手,稍微了解之后就能用它编写出各种程序。
Go 确实非常容易学习。之前就经常听人提到这一点,但实际使用后我仍惊讶于它竟能快速提高工作效率。感谢 Go 语言、相关文档和工具,我只用了短短两天就编写出了有趣且可以提交的代码。 —Rust 程序员对于 Go 语言的早期印象
这里的重点就是“简单性”三个字。当然,简单并不代表容易。可一门小而简单的语言,学起来肯定要比大而复杂的语言要轻松。实现一种效果的方法并不多,所以高质量的 Go 代码看起来几乎都是一个样。这还带来另一个好处:我们可以快速理解某项自己不熟悉的服务到底在做什么。
fmt.Println("Gopher's Diner Breakfast Menu")
for dish, price := range menu {
fmt.Println(dish, price)
}
Go 的核心本体虽然很小,但标准库却非常强大。也就是说,除了 Go 语法之外,我们的学习曲线还必须考虑到标准库这个部分。
另一方面,把功能从语言转移到标准库,意味着大家只需要专注学习跟当前开发需求相关的库。Go 在设计上也充分考虑到大规模软件开发需求,能够有力支持大型代码库和开发团队。在这类场景下,新加入的开发者必须能够快速上手。为此,Go 社区一直将程序的简单、明确、通用和直接放在首位。
使用 Go,我们可以快速完成工作。Go 是我用过的最高效的语言之一,它的座右铭就是:马上解决实际问题。 — Matthias Endler
Rust 比其他几种编程语言支持更多复杂性,所以对应的实现范畴也更大些。 — Devathon
Rust 经过专门设计,包含多种强大且有用的功能,可以帮助程序员用最少的代码完成更多任务。例如,Rust 的匹配功能就可快速编写出灵活且富有表达力的逻辑:
fn is_prime(n: u64) -> bool {
match n {
0...1 => false,
_ => !(2..n).any(|d| n % d == 0),
}
}
但也因为 Rust 在设计上考虑得多,所以学起来也就更困难一点,特别是在起步阶段。但没关系,毕竟 C++ 或者 Java 也有很多东西要学,甚至还无法提供内存安全之类的 Rust 高级功能。所以批评 Rust 太过复杂的声音实在没啥道理:它在设计上就是在强调表达能力和丰富的功能,我们不可能在占了好处的同时又指望着它能那么简单纯粹。
所以 Rust 当然有自己的学习曲线。但只要跨过了这道难关,后面就是一马平川了。
如果您已经准备好学习更复杂的语法和语义(以及更高的代码可读性门槛),并以此换取最高水平的性能表现,那 Rust 甚至足以跟 C++ 和 D 分庭抗礼。 — Dave Cheney
Rust 和 Go 之间虽然彼此借鉴了一些功能(比如说泛型),但公平地讲,Rust 的功能还是更胜一筹,Go 的功能相对要匮乏一点。
大多数语言都为并发编程(即同时执行多项操作)提供某种形式的支持,但 Go 则是从头开始就为此而设计。Go 不使用操作系统线程,而是提供一种轻量化的替代方案:goroutines。每个 goroutine 都是个独立执行的 Go 函数,Go 调度程序会将其映射至控制下的操作系统线程之一。也就是说,调度程序可以非常高效地管理大量并发 goroutine,且只须使用有限数量的操作系统线程。
因此,我们可以在单个程序中运行数百万个并发 goroutine,又不必担心引发严重的性能问题。正因为如此,Go 成为 Web 服务器和微服务等大规模并发应用场景下的完全解决方案。
Go 还为 goroutines 提供 channels,这是一种快速、安全、高效实现数据通信和共享的方法。Go 的并发设计水平确实很高,使用体验也相当轻松愉快。
一般来说,并发程序的设计难度很大,在任何语言中构建起可靠且正确的并发程序都绝非易事。但由于在立项之初就考虑到这方面需求,所以 Go 中的并发编程机制已经做得尽可能简单且得到良好整合。
Go 让我们能更轻松地构建起一个能精心解构的应用程序,这样的应用程序可以作为一组微服务进行部署,并充分发挥并发性优势。Rust 也不是做不到,只是实现起来更难一些。从某种意义上讲,Rust 更适合那些绝不允许因内存问题而引发安全漏洞的程序员;但相应的,他们在执行某些对其他语言(包括 GO)来说较为简单的任务时,就得付出更多心力。 —Sonya Koptyev
相比之下,Rust 中的并发机制刚刚落地、还没有最终稳定,所以欢迎大家继续关注这个活跃的开发方向。这样也有好处,比如 Rust 的 rayon 库就提供一种非常优雅且轻量级的方法,能够将顺序计算转换为并行计算。
能有用于生成 goroutine 和使用 channels 的轻量级语法真的太棒了。这就是语法之力的直接体现,种种小细节也让 Go 的并发编程体验比其他语言好出一大截。 —Rust 程序员对 Go 的早期印象
虽然在 Rust 中实现并发程序可能不太容易,但仍然完全可行,而且这些程序还能获得 Rust 精心设计的内存安全保障。
以标准库的 Mutex 类为例:在 Go 当中,我们可能会在访问某些内容前忘记获取互斥锁;但在 Rust 这边则完全不需要担心。
Go 专注于把并发作为最核心的概念之一。这倒不是说我们就没法在 Rust 中实现跟 Go 类似的并发性效果,只是实现难度对于程序员多少是种考验。 —Dave Cheney
前文已经提到,Go 和 Rust 都会以各自的方式防止各种常见的编程错误,特别是跟内存管理相关的问题。但 Rust 走得更远,可以说是不遗余力地保证大家不致搞出意料之外的安全纰漏。
Rust 的编译器简直是严格到迂腐,它会检查我们使用的每个变量、引用的每个内存地址。它避免了潜在的数据竞争情况,还会通知你存在未定义行为。在 Rust 的世界中,并发和内存安全问题几乎不可能出现。 —为什么选择 Rust?
也就是说,Rust 的编程体验跟几乎所有其他语言都有所不同,而且在刚刚接触时可能相当具有挑战。但在不少开发者看来,这份付出显然物有所值。
对我来说,Rust 最大的优势就是编译器成了我的好助手,它不会放过任何检测得到的 bug(说真的,有时候我感觉它就像会魔法)。 —Grzegorz Nosek
包括 Go 在内,很多语言也提供帮助程序员避免错误的工具,但 Rust 把这种效果提升到了新的水平。很多不正确的程序甚至根本没办法编译。
在 Rust 中,各种库工具都能帮助程序员防止用户犯错。Rust 允许我们指定一段数据,然后保证它不归属于任何其他事物、也不会被任何其他事物所篡改。我想不起以往还有哪种语言会提供这么多防止意外误用的工具,这种感觉堪称美妙。 — Sam Rose
“与借用检查器作斗争”是 Rust 新人们必须要过的一关,但在大多数情况下,检查器并不是真正的敌人。它发现的问题确实是代码中的真实 bug(或者至少是潜在 bug)。它可能迫使我们从根本上重构自己的程序来避免此类问题——如果各位确实把正确性和可靠性当作首要任务,那这种严格要求显然是件好事。
换个角度想,不改变编程方式的新语言,能叫新语言吗?而且在使用其他语言时,Rust 教会我们的安全思维同样意义重大。
如果大家选择了 Rust,往往是因为要使用它提供的保障性设计:关于空指针 / 数据竞争的安全性、可预测的运行时行为,还有对硬件的完全控制。如果这些对你来说毫无意义,那确实没必要非得使用 Rust。毕竟这些好处背后是有代价的:上手很费劲。你得改掉坏习惯并掌握新概念。刚开始的时候,大家都会被借用检查器折磨得死去活来。 — Matthias Endler
上手 Rust 编程模型的实际难度,可能取决于大家之前用过哪些其他语言。Python 或者 Ruby 程序员可能觉得 Rust 限制太多,但其他人可能觉得这种清晰明确的约束也不错。
如果你是一名 C 或者 C++ 程序员,曾经花几个礼拜在语言中查找内存安全 bug,那你一定会爱上 Rust。于是“跟借用检查器作斗争”就变成了“编译器还能这么用?爽!” — Grzegorz Nosek
如今的服务器程序包含着数千万行代码,由成百上千名程序员编写而成,并且几乎每天都在更新。Go 在设计和开发上,充分考虑到了此类环境下的工作效率提升需求。Go 的设计考量因素包括严格的依赖项管理、软件架构随系统增长的适应性,还有跨组件边界的健壮性。 — Rob Pike
当大家独自或者在小团队中解决问题时,要选简单的语言还是丰富的语言纯属个人喜好。但随着软件规模的扩大、复杂度的提升、团队的膨胀,两类语言之间的差异才开始真正显现出来。
对于大型应用程序和分布式系统,代码执行速度的重要性往往低于开发速度:像 Go 这种刻意强调精简设计的语言能够缩短开发新手的适应时间,也让他们能更快参与到大型代码库的贡献当中。
使用 GO 语言,初级开发者往往更容易提高工作效率,但中级开发者则更难引入复杂的抽象并因此导致问题。正因为这种特性,在企业软件开发领域,Rust 的吸引力往往不及 Go。 — Loris Cro
在涉及大规模软件开发时,明确易读总是比精巧优雅更重要。Go 的局限性实际使其比 Rust 等更复杂、更强大的语言,要更适应企业和大型组织的需求。
虽然 Rust 和 Go 都是高人气且得到广泛应用的现代语言,但二者间并不是真正的竞争对手,因为它们所面向的用例可以说完全不同。
Go 的整个编程方法就跟 Rust 完全不同,这些特性一方面特别适合某些人,但另一方面也会彻底激怒某些人。这很正常,因为如果 Rust 和 Go 都在以基本相似的方式解决基本相同的问题,那我们干嘛还需要两种独立的语言?
那么,我们能不能从 Rust 和 Go 采取的方法入手,解读它们各自的本质呢?下面就一起来试试。
“要垃圾收集,还是不要垃圾收集”永远是个没有正确答案的问题。一般来说,垃圾收集和自动内存管理能帮助我们快速、轻松地开发出可靠且高效的程序。所以对某些开发者来说,这些都是必不可少的功能。
但也有人认为,垃圾收集和它带来的性能开销与全局暂停,会导致程序在运行时的行为变得不可预测,同时引入不可接受的延迟。这话当然也有道理。
Go 跟 Rust 这两种语言可以说截然不同。尽管二者都可以被简单描述成系统语言或者 C 的替代品,但它们的目标和应用场景、语言设计风格与功能优先级确实差异巨大。垃圾收集就是一大核心差异因素。Go 中的垃圾收集让语言变得更简单、更小巧也更易于理解。Rust 不设垃圾收集则让它速度极快(这一点特别适合那些不仅要求高吞吐量、更要求低延迟的开发者),同时也实现了 Go 根本不可能做到的一系列功能与编程模式(至少是在不牺牲性能的前提下)。 — PingCAP
计算机编程的发展史,可以说是一段日益复杂的抽象发展历程。它让程序员们既能解决问题,又不用太多关注底层硬件的实际运行方式。
这种设计让程序更易于编写、更具可移植性。但对于其他一些程序来说,访问硬件及精确控制程序的执行方式反而更加重要。
Rust 的目标就是让程序员能“贴近硬件”,夺回更多控制权;而 Go 则抽象掉了架构细节,让程序员更贴近问题。
两种语言各有不同的应用范围。Go 擅长编写微服务和典型的“DevOps”任务,但它并不属于系统编程语言。Rust 在强调并发性、安全性及 / 或性能的任务中更为强大,可学习曲线也确实比 Go 更陡峭。 — Matthias Endler
其实对大多数程序来说,性能的重要性是不及代码可读性的。但如果某些项目确实是以性能为先,那 Rust 中的很多设计权衡,将帮助大家把代码的执行速度一路推向极限。
相比之下,Go 更关心代码简单性,甚至愿意为此牺牲一些运行时性能。但 Go 的构建速度无与伦比,这对大规模代码项目来说往往更加重要。
Rust 的执行速度优于 Go。在基准测试中,Rust 速度确实更快,某些情况下甚至能快出一个数量级。但在选择 Rust 语言之前,请先认清一点:Go 在多数基准测试中也没有落后太多,而且也仍然保持对 Java、C#、JavaScript 和 Python 等语言的性能优势。 如果你需要的是顶级性能,那么在这两种语言中任意选择都可以,速度表现绝不会令人失望。另外,如果你正在构建一款处理高强度负载的 Web 服务,而且要求能够纵向 / 横向灵活扩展,两款语言也都能满足需求。 — Andrew Lader
另一方面,如果不强求程序永不出错,那取舍以会不同。大多数代码都不会考虑到长期使用,但某些程序也确实在生产环境中多年运行。
面对这些现实情况,也许我们有必要投入一点额外的时间,来开发并保证程序能够正确、可靠运行,且未来不致引发沉重的维护负担。
Go 和 Rust 都能帮助大家编写出正确的程序,只是具体方式各有不同:Go 提供出色的内置测试框架,而 Rust 则专注通过借用检查器消除运行时 bug。
我的看法是:对于明天就得发布的代码,用 Go;如果是未来五年内必须能稳定运行的代码,那么选 Rust。 — Grzegorz Nosek
虽然 Go 和 Rust 都足以支撑起严肃的开发项目,但大家最好还是能充分了解它们的各种特性和优势。
总之,别人的想法并不重要:只有你自己能决定哪种编程语言更适合你的团队及项目需求。
如果你想加快开发速度,比如说你有很多不同服务需要编写,或者开发团队本身规模庞大,那么 Go 语言肯定是正确答案。Go 特别关注并发性设计,而且会敏锐地揪出不安全的内存访问行为(Rust 也可以),但又不强迫你逐一管理每处细节。 Go 快速而强大,但它的核心亮点还是帮助开发人员摆脱困境、专注于简单性和统一性。在另一方面,如果你需要竭尽全力发挥每一丝性能空间,那 Rust 才是最理想的选择。 —Andrew Lader
总结希望这篇文章能帮助大家理解 Rust 和 Go 的各自亮点。如果可能的话,各位最好能多少体验一下这两种语言,因为它们在任何技术道路上都非常有用,哪怕对业余编程爱好者也是如此。
但如果您的时间只搞认真钻研一门语言,那请千万先把 Go 和 Rust 各自的专长和倾向性搞清楚,之后再做选择。
当然,关于编程语言的知识只是成就一名成功软件工程师的很少一部分。除了编程之外,工程师们还得精通设计、工程、架构、沟通和协作。只要大家能把后面这几样做好,那无论你选择哪种编程语言,都将成为一名出色的软件工程大牛。
工信部要求所有 App、小程序备案;某国产电商被提名 Pwnie Awards “最差厂商奖”;阿里财报超预期 | Q资讯
生成的代码会出错、质量差?面对 AI 编程工具的老大难问题,华为这群人打算这样做InfoQ 研究中心近日重磅发布《中国开源生态系列图谱——前端领域》,该报告覆盖100+前端开源项目,打造前端开源生态图谱,依据 InfoQ 开源项目指数盘点中国前端开源 Top10 项目,总结三大核心发展要素,帮助开发者群体更深入地理解前端开源领域的发展现状和未来发展趋势。扫描下方二维码了解详情。
以「启航·AIGC 软件工程变革」为主题的 QCon 全球软件开发大会·北京站将于 9 月 3-5 日在北京•富力万丽酒店举办,此次大会策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近 30 个精彩专题。
读者福利
👆 腾讯技术实践案例(必备)👆